package com.sky.business.sys.service.impl;

import com.github.dockerjava.api.exception.NotFoundException;
import com.sky.business.sys.domain.*;
import com.sky.business.sys.handler.exception.NoMatchNodeException;
import com.sky.business.sys.service.*;
import com.sky.common.config.CacheConstants;
import com.sky.common.docker.ContainerClient;
import com.sky.common.docker.ContainerOperateService;
import com.sky.common.docker.build.ContainerBuilder;
import com.sky.common.docker.build.ContainerTemplate;
import com.sky.common.enums.*;
import com.sky.common.node.matcher.NodeMathcher;
import com.sky.common.node.matcher.ResourceSufficientNodeMathcher;
import com.sky.common.node.sort.NodeSorter;
import com.sky.common.node.sort.SystemLoadNodeSorter;
import com.sky.common.utils.DateUtils;
import com.sky.common.utils.NetworkUtil;
import com.sky.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

/**
 * 任务创建、删除服务
 */
@Slf4j
@Service
public class TaskServiceImpl implements TaskService {

    @Autowired
    private ContainerOperateService containerOperateService;

    @Autowired
    private ContainerService containerService;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private ContainerConfigService containerConfigService;

    @Autowired
    private SystemConfigService systemConfigService;

    private static NodeMathcher nodeMathcher = new ResourceSufficientNodeMathcher();

    private static NodeSorter nodeSorter = new SystemLoadNodeSorter();

    @Override
    public void removeContainer(ContainerClient client, Container container) {
        try {
            containerOperateService.remove(client, container.getDockerId(), true);
        } catch (NotFoundException e) {
            log.error("未发现容器,节点id:{}, 容器名称:{}", container.getNodeId(), container.getName(), e);
        }
        container.setIsDel(true);
        containerService.updateById(container);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startMonitorTask(SystemConfig systemConfig, Node node, ContainerConfig containerConfig) {
        //删除旧任务
        removeMonitorTask(node);
        //生成容器名称
        ContainerClient client;
        if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
            client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
        }else{
            client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
        }
        //创建容器
        String image = containerConfig.getImageName() + ":" + containerConfig.getImageTag();
        ContainerBuilder containerBuilder = ContainerBuilder.create().withName(CacheConstants.MONITOR_CONTAINER_NAME).useImage(image);
        RestartPolicy restartPolicy = containerConfig.getRestartPolicy();
        if(restartPolicy== RestartPolicy.ALWAYS){
            containerBuilder.alwaysRestart();
        }else if(restartPolicy == RestartPolicy.ON_FAILURE){
            containerBuilder.onFailureRestart();
        }else if(restartPolicy == RestartPolicy.UNLESS_STOPPED){
            containerBuilder.unlessStopRestart();
        }
        if(containerConfig.getHostMode()!=null && containerConfig.getHostMode()){
            containerBuilder.useHostMode();
        }
        if (node.getGpuCount() > 0) {
            containerBuilder.addGpuSupport();
        }
        ContainerTemplate containerTemplate = containerBuilder.addEnv("EDGE_ID", systemConfig.getEdgeId()).addEnv("NODE_ID", String.valueOf(node.getId())).addEnv("MQTT_HOST", systemConfig.getLocalMQTTServiceHost()).addEnv("MQTT_PORT", systemConfig.getLocalMQTTServicePort()).addEnv("MQTT_USERNAME", systemConfig.getLocalMQTTServiceUsername()).addEnv("MQTT_PASSWORD", systemConfig.getLocalMQTTServicePassword()).addEnv("MQTT_QOS", "0").addEnv("REPORT_TOPIC", systemConfig.getLocalReportingTopic()).addVolume("/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime", true).build();
        String containerId = containerOperateService.create(client, containerTemplate);
        //启动容器
        containerOperateService.start(client, containerId);
        //保存记录
        saveContainer(containerId,CacheConstants.MONITOR_CONTAINER_NAME,containerConfig,node,0);
    }

    @Override
    public void removeMonitorTask(Node node) {
        SystemConfig systemConfig = systemConfigService.getSystemConfig();
        ContainerClient client;
        if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
            client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
        }else{
            client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
        }
        //删除旧容器
        List<Container> containerList = containerService.findByNodeAndTaskType(node.getId(), TaskType.MONITOR);
        for (Container container : containerList) {
            removeContainer(client, container);
        }
    }


    /**
     * 选择最合适的node
     *
     * @param cameraConfig 设备
     * @param taskType     任务类型
     * @param archMap      架构与容器配置对应map
     * @return node 选择的节点
     */
    private Node selectNode(CameraConfig cameraConfig, TaskType taskType, Map<Arch, ContainerConfig> archMap) {
        //查找和设备同组、最近1分钟内有心跳，且cpu架构在支持范围内的所有节点
        Date lastHeartbeat = DateUtils.from(LocalDateTime.now().plusSeconds(-CacheConstants.NODE_HEARTBEAT_TIMEOUT_SECOND));
        List<Node> nodeList = nodeService.findByGroupAndLastHbAfterAndArchIn(cameraConfig.getGroupId(), lastHeartbeat, archMap.keySet());
        if (nodeList.isEmpty()) {
            throw new NoMatchNodeException("未找到与设备同组已就绪的有效节点,deviceId:" + cameraConfig.getCameraDeviceId());
        }
        //查询与任务匹配的节点
        List<Node> matchedNodeList = new ArrayList<>();
        for (Node node : nodeList) {
            ContainerConfig currContainerConfig = archMap.get(node.getArch());
            if (currContainerConfig == null) {
                continue;
            }
            boolean match = nodeMathcher.match(node, currContainerConfig);
            if (match) {
                matchedNodeList.add(node);
            }
        }
        if (matchedNodeList.isEmpty()) {
            throw new NoMatchNodeException("未找到与任务匹配的节点,deviceId:" + cameraConfig.getCameraDeviceId());
        }
        if (matchedNodeList.size() == 1) {
            return matchedNodeList.get(0);
        }
        //排序
        boolean gpuSupport = taskType == TaskType.AI_TRACK;
        List<Node> sortedNodeList = nodeSorter.sort(matchedNodeList, gpuSupport);
        for (int i = 0; i < sortedNodeList.size(); i++) {
            Node currNode = sortedNodeList.get(i);
            boolean reached = NetworkUtil.isReachable(currNode.getIpAddr(), currNode.getContainerPort(), 5000);
            if (reached) {
                return currNode;
            }
        }
        throw new NoMatchNodeException("未找到容器服务端口开发的节点,deviceId:" + cameraConfig.getCameraDeviceId());
    }


    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void startPubStreamTask(SystemConfig systemConfig, CameraConfig cameraConfig) {
        //删除旧任务
        removePubStreamTask(cameraConfig);
        //查询镜像支持的cpu架构
        List<ContainerConfig> containerConfigList = containerConfigService.findByTaskType(TaskType.PUB_STREAM);
        if (containerConfigList.isEmpty()) {
            throw new NoMatchNodeException("转推任务镜像不存在");
        }
        Map<Arch, ContainerConfig> archMap = new HashMap<>(2);
        for (ContainerConfig config : containerConfigList) {
            if (!archMap.containsKey(config.getImageArch())) {
                archMap.put(config.getImageArch(), config);
            }
        }
        //选择节点
        Node node = selectNode(cameraConfig, TaskType.PUB_STREAM, archMap);
        log.info("选择节点-------------------------------------");
        log.info("nodeId:{} ,group id:{}, cameraConfig group Id:{}",node.getId(),node.getGroupId(),cameraConfig.getGroupId());

        //节点对应的架构
        ContainerConfig containerConfig = archMap.get(node.getArch());
        //创建容器
        ContainerClient client;
        if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
            client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
        }else{
            client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
        }
        String image = containerConfig.getImageName() + ":" + containerConfig.getImageTag();
        //构建容器名
        String containerName = CacheConstants.STREAM_CONTAINER_NAME_PREFIX + cameraConfig.getCameraDeviceId().substring(16);
        ContainerBuilder containerBuilder = ContainerBuilder.create().withName(containerName).useImage(image);
        RestartPolicy restartPolicy = containerConfig.getRestartPolicy();
        if(restartPolicy== RestartPolicy.ALWAYS){
            containerBuilder.alwaysRestart();
        }else if(restartPolicy == RestartPolicy.ON_FAILURE){
            containerBuilder.onFailureRestart();
        }else if(restartPolicy == RestartPolicy.UNLESS_STOPPED){
            containerBuilder.unlessStopRestart();
        }
        if(containerConfig.getHostMode()!=null && containerConfig.getHostMode()){
            containerBuilder.useHostMode();
        }
        ContainerTemplate containerTemplate = containerBuilder.addEnv("EDGE_ID", systemConfig.getEdgeId()).addEnv("DEVICE_ID", String.valueOf(cameraConfig.getCameraDeviceId())).
                addEnv("PULL_STREAM_URL", cameraConfig.getPullStream()).addEnv("PUSH_STREAM_URL", cameraConfig.getPushStream()).addVolume("/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime", true).build();
        String containerId = containerOperateService.create(client, containerTemplate);
        //启动容器
        containerOperateService.start(client, containerId);
        //保存记录
        saveContainer(containerId,containerName,containerConfig,node,cameraConfig.getId().intValue());
    }

    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void removePubStreamTask(CameraConfig cameraConfig) {
        SystemConfig systemConfig = systemConfigService.getSystemConfig();
        List<Container> containerList = containerService.findByCameraIdAndTaskType(cameraConfig.getId(), TaskType.PUB_STREAM);
        for (Container container : containerList) {
            Node node = nodeService.getById(container.getNodeId());
            ContainerClient client;
            if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
                client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
            }else{
                client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
            }
            removeContainer(client, container);
        }
    }

    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void startAiPubStreamTask(SystemConfig systemConfig, CameraConfig cameraConfig) {
        //删除旧任务
        removeAiPubStreamTask(cameraConfig);
        //查询镜像支持的cpu架构
        List<ContainerConfig> containerConfigList = containerConfigService.findByTaskType(TaskType.PUB_STREAM);
        if (containerConfigList.isEmpty()) {
            throw new NoMatchNodeException("AI转推任务镜像不存在");
        }
        Map<Arch, ContainerConfig> archMap = new HashMap<>(2);
        for (ContainerConfig config : containerConfigList) {
            if (!archMap.containsKey(config.getImageArch())) {
                archMap.put(config.getImageArch(), config);
            }
        }
        //选择节点
        Node node = selectNode(cameraConfig, TaskType.PUB_STREAM, archMap);
        log.info("选择节点-------------------------------------");
        log.info("nodeId:{} ,group id:{}, cameraConfig group Id:{}",node.getId(),node.getGroupId(),cameraConfig.getGroupId());

        //节点对应的架构
        ContainerConfig containerConfig = archMap.get(node.getArch());
        containerConfig.setTaskType(TaskType.AI_PUB_STREAM);
        //创建容器
        ContainerClient client;
        if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
            client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
        }else{
            client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
        }
        String image = containerConfig.getImageName() + ":" + containerConfig.getImageTag();
        //构建容器名
        String containerName = CacheConstants.AI_STREAM_CONTAINER_NAME_PREFIX + cameraConfig.getCameraDeviceId().substring(16);
        ContainerBuilder containerBuilder = ContainerBuilder.create().withName(containerName).useImage(image);
        RestartPolicy restartPolicy = containerConfig.getRestartPolicy();
        if(restartPolicy== RestartPolicy.ALWAYS){
            containerBuilder.alwaysRestart();
        }else if(restartPolicy == RestartPolicy.ON_FAILURE){
            containerBuilder.onFailureRestart();
        }else if(restartPolicy == RestartPolicy.UNLESS_STOPPED){
            containerBuilder.unlessStopRestart();
        }
        if(containerConfig.getHostMode()!=null && containerConfig.getHostMode()){
            containerBuilder.useHostMode();
        }
        ContainerTemplate containerTemplate = containerBuilder.addEnv("EDGE_ID", systemConfig.getEdgeId()).addEnv("DEVICE_ID", String.valueOf(cameraConfig.getCameraDeviceId())).
                addEnv("PULL_STREAM_URL", cameraConfig.getAiPullStream()).addEnv("PUSH_STREAM_URL", cameraConfig.getAiPushStream()).addVolume("/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime", true).build();
        String containerId = containerOperateService.create(client, containerTemplate);
        //启动容器
        containerOperateService.start(client, containerId);
        //保存记录
        saveContainer(containerId,containerName,containerConfig,node,cameraConfig.getId().intValue());
    }

    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void removeAiPubStreamTask(CameraConfig cameraConfig) {
        SystemConfig systemConfig = systemConfigService.getSystemConfig();
        List<Container> containerList = containerService.findByCameraIdAndTaskType(cameraConfig.getId(), TaskType.AI_PUB_STREAM);
        for (Container container : containerList) {
            Node node = nodeService.getById(container.getNodeId());
            ContainerClient client;
            if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
                client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
            }else{
                client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
            }
            removeContainer(client, container);
        }
    }

    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void startTrackTask(SystemConfig systemConfig, CameraConfig cameraConfig) {
        //删除旧任务
        removeTrackTask(cameraConfig);
        //查询镜像支持的cpu架构
        List<ContainerConfig> containerConfigList;
        if(cameraConfig.getControlMode()==ControlMode.onvif) {
            containerConfigList = containerConfigService.findByTaskType(TaskType.AI_TRACK);
        }else{
            containerConfigList = containerConfigService.findByTaskType(TaskType.PTD_TRACK);
        }
        if (containerConfigList.isEmpty()) {
            throw new NoMatchNodeException("跟踪任务镜像不存在");
        }
        Map<Arch, ContainerConfig> archMap = new HashMap<>(2);
        for (ContainerConfig config : containerConfigList) {
            if (!archMap.containsKey(config.getImageArch())) {
                archMap.put(config.getImageArch(), config);
            }
        }
        //选择节点
        Node node;
        if(cameraConfig.getControlMode()==ControlMode.onvif) {
            node = selectNode(cameraConfig, TaskType.AI_TRACK, archMap);
        }else{
            node = selectNode(cameraConfig,TaskType.PTD_TRACK,archMap);
        }
        //节点对应的架构
        ContainerConfig containerConfig = archMap.get(node.getArch());
        //创建容器
        ContainerClient client;
        if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
            client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
        }else{
            client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
        }
        String image = containerConfig.getImageName() + ":" + containerConfig.getImageTag();
        //构建容器名
        String containerName = CacheConstants.TRACK_CONTAINER_NAME_PREFIX + cameraConfig.getCameraDeviceId().substring(16);
        ContainerBuilder containerBuilder = ContainerBuilder.create().withName(containerName).useImage(image);
        if(containerConfig.getHostMode()!=null && containerConfig.getHostMode()){
            containerBuilder.useHostMode();
        }
        RestartPolicy restartPolicy = containerConfig.getRestartPolicy();
        if(restartPolicy== RestartPolicy.ALWAYS){
            containerBuilder.alwaysRestart();
        }else if(restartPolicy == RestartPolicy.ON_FAILURE){
            containerBuilder.onFailureRestart();
        }else if(restartPolicy == RestartPolicy.UNLESS_STOPPED){
            containerBuilder.unlessStopRestart();
        }

        if(cameraConfig.getControlMode()==ControlMode.onvif) {
            containerBuilder.withRuntime("nvidia").addGpuSupport().addEnv("NVIDIA_DRIVER_CAPABILITIES", "video,compute,utility");
        }else{
            containerBuilder.addEnv("CAMERA_IP",cameraConfig.getCameraIp());
            Integer ptzPort = cameraConfig.getPtdPort();
            if (ptzPort == null || ptzPort < 1 || ptzPort > 65535) {
                ptzPort = CacheConstants.DEFAULT_PTD_PORT;
            }
            containerBuilder.addEnv("PTD_PORT",String.valueOf(ptzPort));
        }

        //创建容器
        containerBuilder.useShareMemory(containerConfig.getSharedMemory() * 1024L * 1024L)
        .useMemory(containerConfig.getMemory() * 1024L * 1024L)
        .useSwapMemory(containerConfig.getSwapMemory() * 1024L * 1024L)
        .useKernelMemory(containerConfig.getKernelMemory() * 1024L * 1024L)
        .addEnv("EDGE_ID", systemConfig.getEdgeId())
        .addEnv("DEVICE_ID", cameraConfig.getCameraDeviceId())
        .addEnv("MQTT_HOST", systemConfig.getLocalMQTTServiceHost())
        .addEnv("MQTT_PORT", systemConfig.getLocalMQTTServicePort())
        .addEnv("MQTT_USERNAME", systemConfig.getLocalMQTTServiceUsername())
        .addEnv("MQTT_PASSWORD", systemConfig.getLocalMQTTServicePassword())
        .addEnv("DISPATCHER_TOPIC", cameraConfig.getDispatcherTopic())
        .addEnv("REPORT_TOPIC", systemConfig.getLocalReportingTopic())
        .addVolume("/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime", true).build();
        ContainerTemplate containerTemplate = containerBuilder.build();
        String containerId = containerOperateService.create(client, containerTemplate);
        //启动容器
        containerOperateService.start(client, containerId);
        //保存记录
        saveContainer(containerId,containerName,containerConfig,node,cameraConfig.getId().intValue());
    }

    @Override
    @Async("miniThreadPoolTaskExecutor")
    public void removeTrackTask(CameraConfig cameraConfig) {
        SystemConfig systemConfig = systemConfigService.getSystemConfig();
        List<TaskType> taskTypeList = new ArrayList<>();
        taskTypeList.add(TaskType.AI_TRACK);
        taskTypeList.add(TaskType.PTD_TRACK);
        List<Container> containerList = containerService.findByCameraIdAndTaskTypeIn(cameraConfig.getId(), taskTypeList);
        for (Container container : containerList) {
            Node node = nodeService.getById(container.getNodeId());
            ContainerClient client;
            if(StringUtils.isEmpty(systemConfig.getCertificatePath())) {
                client = new ContainerClient(node.getIpAddr(), node.getContainerPort());
            }else{
                client = new ContainerClient(node.getIpAddr(),node.getContainerPort(),systemConfig.getCertificatePath());
            }
            removeContainer(client, container);
        }
    }

    @Override
    public boolean isTrackRunning(CameraConfig cameraConfig) {
        TaskType taskType;
        if(cameraConfig.getControlMode()==ControlMode.onvif){
            taskType = TaskType.AI_TRACK;
        }else{
            taskType = TaskType.PTD_TRACK;
        }
        List<Container> containerList = containerService.findByCameraIdAndTaskType(cameraConfig.getId(),taskType);
        if(containerList.isEmpty()){
            return false;
        }
        for(Container container:containerList){
            if(container.getStatus()==ContainerStatus.running){
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isPubStreamRunning(CameraConfig cameraConfig) {
        List<Container> containerList = containerService.findByCameraIdAndTaskType(cameraConfig.getId(),TaskType.PUB_STREAM);
        if(containerList.isEmpty()){
            return false;
        }
        for(Container container:containerList){
            if(container.getStatus()==ContainerStatus.running){
                return true;
            }
        }
        return false;
    }

    /**
     * 容器表保存记录
     * @param containerId 容器id
     * @param containerName 容器名称
     * @param containerConfig 容器运行配置
     * @param node 运行节点
     * @param cameraConfigId 设备id
     */
    public void saveContainer(String containerId, String containerName, ContainerConfig containerConfig, Node node, Integer cameraConfigId){
        Container container = new Container();
        container.setDockerId(containerId);
        container.setName(containerName);
        container.setConfigId(containerConfig.getId());
        container.setImage(containerConfig.getImageName() + ":" + containerConfig.getImageTag());
        container.setArch(containerConfig.getImageArch());
        container.setTaskType(containerConfig.getTaskType());
        container.setStatus(ContainerStatus.running);
        container.setNodeId(node.getId());
        container.setCameraConfigId(cameraConfigId);
        containerService.save(container);
    }
}
